#include "fiber.h"
#include "base/Log.h"
#include "base/config.h"
#include "base/marco.h"
#include "base/scheduler.h"
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <functional>
#include <sys/types.h>
#include <ucontext.h>

namespace Yizhi {


    static ConfigVar<size_t> ::ptr g_stacksize=
        Config::Lookup<size_t>("fiber.stack.size",1024*1024,"fiber stack size");
    

    static Logger::ptr g_logger=SERVER_LOG_NAME("system");
    std::atomic<uint64_t> s_fiber_count={0};
    std::atomic<uint64_t> s_fiber_id={0};

    ///@brief 主协程
    thread_local Fiber::ptr t_mainfiber(nullptr);
    ///@brief 任务协程
    thread_local Fiber* t_fiber(nullptr);


    //分配栈空间
    struct MallocStackAlloctor{
        static void Dealloc(size_t size,void* vp){
            free(vp);
        }
        static void* Alloct(size_t size){
            return malloc(size);
        }
    };

    using StackAlloctor=MallocStackAlloctor;

    uint64_t Fiber::GetFiberId(){
        if(t_fiber){
            return t_fiber->m_id;
        }
        return 0;
    }

    ///@brief 主协程构造
    Fiber::Fiber(){
        m_state=EXEC;
        if(getcontext(&m_ctxt)){
            SERVER_ASSERT_MSG(false, "getcontext failed");
        }
        SetThis(this);
        ++s_fiber_count;


        SERVER_LOG_DEBUG(g_logger)<<"Fiber id :"<<m_id;
    }

    ///@brief 任务协程构造
    Fiber::Fiber(std::function<void()> cb,bool user_caller,size_t stacksize)
    :m_cb(cb)
    ,m_id(++s_fiber_id)
    {
        ++s_fiber_count;
        stacksize=stacksize?stacksize:g_stacksize->getvalue();
        m_stacksize=stacksize;
        if(getcontext(&m_ctxt)){
            SERVER_ASSERT_MSG(false, "getcontext failed");
        }

        m_stack=StackAlloctor::Alloct(m_stacksize);
        m_ctxt.uc_link=nullptr;
        m_ctxt.uc_stack.ss_sp=m_stack;
        m_ctxt.uc_stack.ss_size=m_stacksize;
        if(user_caller){
             makecontext(&m_ctxt,&Fiber::CallMainFUnc,0);
        }
        else{
             makecontext(&m_ctxt,&Fiber::MainFunc,0);
        }
       
        SERVER_LOG_DEBUG(g_logger)<<"Fiber id :"<<m_id;
    }


    Fiber::~Fiber(){
        //带栈为任务协程
        if(m_stack){
            SERVER_ASSERT(m_state==TERM||m_state==INIT||m_state==EXECPT);
            StackAlloctor::Dealloc(m_stacksize,m_stack);
            SERVER_LOG_DEBUG(g_logger)<<"~ task fiber:"<<m_id; 
        }

        //主协程的判断
        else{
            SERVER_ASSERT(!m_cb);
            SERVER_ASSERT(m_state==EXEC);

            Fiber* cur=t_fiber;
            if(cur==this){
                SetThis(nullptr);
            }
             SERVER_LOG_DEBUG(g_logger)<<"~ main fiber:  "<<m_id;  
        }
            
    }


    ///@重置
    void Fiber::reset(std::function<void()> cb){
        SERVER_ASSERT(m_stack);
        SERVER_ASSERT(m_state==INIT||m_state==TERM);

        m_cb=cb;
        if(getcontext(&m_ctxt)){
            SERVER_ASSERT_MSG(false, "getcontext failed");
        }
        m_ctxt.uc_link=nullptr;
        m_ctxt.uc_stack.ss_sp=m_stack;
        m_ctxt.uc_stack.ss_size=m_stacksize;

        makecontext(&m_ctxt,&Fiber::MainFunc, 0);
        m_state=INIT;
    }

    ///@brief 设置当前运行协程
    void Fiber::SetThis(Fiber *f){
        t_fiber=f;
    }


    ///@brief 返回当前协程
    Fiber::ptr Fiber::GetThis(){
        //当前协程存在
        if(t_fiber){
            return t_fiber->shared_from_this();
        }
        //否则
        Fiber::ptr main_fiber(new Fiber);
        //当前协程应该是为主协程
        SERVER_ASSERT(main_fiber.get()==t_fiber);
        
        t_mainfiber=main_fiber;
        return t_fiber->shared_from_this();
    }


    void Fiber::YieldToHold(){
        auto cur=GetThis();
        if(cur->m_state!=EXEC){
            SERVER_LOG_DEBUG(g_logger)<<cur->m_id<<" "<<cur->m_state;
        }
        SERVER_ASSERT(cur->m_state==EXEC);
        cur->m_state=HOLD;
        cur->yield();
    }
    void Fiber::YieldToReady(){
        auto cur=GetThis();
        SERVER_ASSERT(cur->m_state==EXEC);
        cur->m_state=READY;
        cur->yield();
    }

    void Fiber::resunme(){
        SetThis(this);
        SERVER_ASSERT(m_state!=EXEC);
        m_state=EXEC;
        if(swapcontext(&Scheduler::GetMainFiber()->m_ctxt, &m_ctxt)){
            SERVER_ASSERT_MSG(false, "Swapcontext failed");
        }
    }

    void Fiber::yield(){
        SetThis(Scheduler::GetMainFiber());
        if(swapcontext(&m_ctxt, &Scheduler::GetMainFiber()->m_ctxt)){
            SERVER_ASSERT_MSG(false, "swapcontext failed");
        }
    }

    void Fiber::call(){
        SetThis(this);
        SERVER_ASSERT(m_state!=EXEC);
        m_state=EXEC;
        if(swapcontext(&t_mainfiber->m_ctxt, &m_ctxt)){
            SERVER_ASSERT_MSG(false, "swapcontext failed");
        }
    }

    void Fiber::back(){
        SetThis(t_mainfiber.get());
        if(swapcontext(&m_ctxt, &t_mainfiber->m_ctxt)){
            SERVER_ASSERT_MSG(false, "swapcontext failed");
        }
    }

    void Fiber::MainFunc(){
        auto cur=GetThis();
        SERVER_ASSERT(cur);
        try{
            cur->m_cb();
            cur->m_cb=nullptr;
            cur->m_state=TERM;
            
            
        }
        catch(std::exception &e){
            cur->m_state=EXECPT;
            SERVER_LOG_ERROR(g_logger)<<"Fiber exeception"<<e.what();
        }
        catch(...){
            cur->m_state=EXECPT;
            SERVER_LOG_ERROR(g_logger)<<"fiber Exeception";
        }
        auto rw_ptr=cur.get();
        cur.reset();
        rw_ptr->yield();
    }

    void Fiber::CallMainFUnc(){
        auto cur=GetThis();
        SERVER_ASSERT(cur);
        try{
            cur->m_cb();
            cur->m_cb=nullptr;
            cur->m_state=TERM;
            
            
        }
        catch(std::exception &e){
            cur->m_state=EXECPT;
            SERVER_LOG_ERROR(g_logger)<<"Fiber exeception"<<e.what();
        }
        catch(...){
            cur->m_state=EXECPT;
            SERVER_LOG_ERROR(g_logger)<<"fiber Exeception";
        }
        auto rw_ptr=cur.get();
        cur.reset();
        rw_ptr->back();
    }
}